package com.biz.primus.ms.payment.channel.allinpay.lang;

import com.biz.primus.common.utils.StringUtil;
import com.biz.primus.ms.payment.channel.wechat.exceptions.SignException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.util.*;

import static com.google.common.collect.Lists.newArrayList;

/**
 * SimpleSigner
 *
 * @author JKLiues
 * @date 2017年11月10日
 */
@Slf4j
@Getter
public class SimpleSigner {


    public SimpleSigner() {
    }

    public String sign(Set<String> signParamNames, Object object) {
        try {
            Properties property = getProperty(signParamNames, object);
            return sign(signParamNames, property);
        } catch (Exception e) {
            throw new SignException(e);
        }
    }

    public String signWithKey(Set<String> signParamNames, Object object, String key) {
        try {
            Properties property = getProperty(signParamNames, object);
            return sign(signParamNames, property, key);
        } catch (Exception e) {
            throw new SignException(e);
        }
    }

    public String sign2(Set<String> signParamNames, Object object) {
        try {
            Properties property = getProperty(signParamNames, object);
            return sign2(signParamNames, property);
        } catch (Exception e) {
            throw new SignException(e);
        }
    }

    public String signWithKey2(Set<String> signParamNames, Object object, String key) {
        try {
            Properties property = getProperty(signParamNames, object);
            return sign2(signParamNames, property, key);
        } catch (Exception e) {
            throw new SignException(e);
        }
    }

    public String sign(Set<String> signParamNames, Object object, String key) {
        try {
            Properties property = getProperty(signParamNames, object);
            return sign(signParamNames, property, key);
        } catch (Exception e) {
            throw new SignException(e);
        }
    }

    private String sign(Set<String> signParamNames, Properties properties, String key) throws IllegalArgumentException {
        validateProperties(properties);
        validateSignParamNames(signParamNames);
        validateProperties(properties);
        List<String> keyValuePair = newArrayList();
        keyValuePair.add(key);
        for (String signParamName : signParamNames) {
            String signParamValue = properties.getProperty(signParamName);
            if (signParamValue != null) {
                keyValuePair.add(signParamName + "=" + signParamValue);
            }
        }
        keyValuePair.add(key);
        String finalParams = StringUtils.join(keyValuePair, "&");
        return StringUtil.encodedByMD5(finalParams).toUpperCase();
    }

    private String sign(Set<String> signParamNames, Properties properties) throws IllegalArgumentException {
        validateProperties(properties);
        validateSignParamNames(signParamNames);
        validateProperties(properties);
        List<String> keyValuePair = newArrayList();
        for (String signParamName : signParamNames) {
            String signParamValue = properties.getProperty(signParamName);
            if (signParamValue != null) {
                keyValuePair.add(signParamName + "=" + signParamValue);
            }
        }
        String finalParams = StringUtils.join(keyValuePair, "&");
        return StringUtil.encodedByMD5(finalParams).toUpperCase();
    }

    @SuppressWarnings("unchecked")
    private String sign2(Set<String> signParamNames, Properties properties) throws IllegalArgumentException {
        validateProperties(properties);
        validateSignParamNames(signParamNames);
        validateProperties(properties);
        List<String> keyValuePair = newArrayList();
        for (String signParamName : signParamNames) {
            String signParamValue = properties.getProperty(signParamName);
            if (signParamValue != null) {
                keyValuePair.add(String.format("%s%s", signParamName, signParamValue));
            }
        }
        String finalParams = StringUtils.join(keyValuePair);
        return StringUtil.encodedByMD5(finalParams).toUpperCase();
    }

    @SuppressWarnings("unchecked")
    private String sign2(Set<String> signParamNames, Properties properties, String key) throws IllegalArgumentException {
        validateProperties(properties);
        validateSignParamNames(signParamNames);
        validateProperties(properties);
        List<String> keyValuePair = newArrayList();
        keyValuePair.add(key);
        for (String signParamName : signParamNames) {
            String signParamValue = properties.getProperty(signParamName);
            if (signParamValue != null) {
                keyValuePair.add(String.format("%s%s", signParamName, signParamValue));
            }
        }
        keyValuePair.add(key);
        String finalParams = StringUtils.join(keyValuePair,"");
        return StringUtil.encodedByMD5(finalParams).toUpperCase();
    }

    private void validateSignParamNames(Set<String> signParamNames) {
        if (CollectionUtils.isEmpty(signParamNames)) {
            throw new IllegalArgumentException("Argument signParamNames can not be empty.");
        }
    }

    private void validateProperties(Properties properties) {
        if (properties == null || properties.isEmpty()) {
            throw new IllegalArgumentException("Argument properties can not be empty.");
        }
    }

    private Properties getProperty(Collection<String> params, Object object) throws NoSuchFieldException, IllegalAccessException {
        Class<?> aClass = object.getClass();
        Properties properties = new Properties();
        Field[] declaredFields = aClass.getDeclaredFields();
        if (ArrayUtils.isEmpty(declaredFields)) {
            throw new IllegalArgumentException("Argument object not have any fields.");
        }
        for (String name : params) {
            Field declaredField = aClass.getDeclaredField(name);
            declaredField.setAccessible(true);
            Object value = declaredField.get(object);
            if (Objects.nonNull(value)) {
                properties.put(name, String.valueOf(value));
            }
        }
        return properties;
    }
}